package edu.gatech.cs3240;

import java.util.ArrayList;

/**
 * Implements the scanner portion of our parser. Detects invalid characters
 * and maintains a list of tokens to be used later in the parse.
 * @author DPrince
 */
public class Scanner {
  
  /** A list of characters in the alphabet of our language */
	private static char[] language = {'a', 'b', '*', '+', '(', ')',
			'.', '|', '?'};
	
	/** Storage for the (terminal) tokens found by the scanner */
	private ArrayList<Token.Terminal> tokens;
	
	/** The index of the token currently being accessed by a calling class */
	private int index;
	
	/** The input string being scanned */
	private String sourceString;
	
	/**
	 * Returns a character array of the valid characters in the alphabet.
	 * @return Array of valid characters
	 */
	public static char[] getLanguage(){
		return language;
	}
	
	/**
	 * Constructor -- sets up instance variables and begins the scan
	 * @param in The string to be scanned
	 * @throws BadCharacterException Thrown if an invalid character is found
	 */
	public Scanner(String in) throws BadCharacterException{
		this.sourceString = in;
		scan(in);
		index = 0;
	}
	
	/**
	 * Performs the scanning operation, throwing an exception if a bad character
	 * is encountered. Returning without throwing an exception marks success.
	 * @param in The string to be scanned
	 * @throws BadCharacterException Thrown if an invalid character is found
	 */
	private void scan(String in) throws BadCharacterException {
		tokens = new ArrayList<Token.Terminal>(in.length());
		for(int i = 0; i < in.length(); i++) {
			if(isAccepted(in.charAt(i))) {
				tokens.add(new Token.Terminal(in.charAt(i)));
			} else {
				throw new BadCharacterException(in.charAt(i), i);
			}
		}
	}
	
	/**
	 * Returns the original string being scanned
	 * @returns The string being scanned by this scanner
	 */
	public String getSourceString() {
		return sourceString;
	}
	
	/**
	 * Peeks at the next terminal on the stack
	 * @return The terminal at the next stack index
	 */
	public Token.Terminal peek() {
		if(index < tokens.size())
			return tokens.get(index + 1);
		else
			return new Token.Terminal('$');
	}
	
	/**
	 * Shows the terminal token at the current stack index
	 * @return The current terminal
	 */
	public Token.Terminal showCurrent() {
		if(index < tokens.size()) {
			return tokens.get(index);
		} else {
			return new Token.Terminal('$');
		}
	}
	
	/**
	 * Returns the scanner to the beginning of the stack
	 */
	public void resetIndex() {
		index = 0;
	}
	
	/**
	 * Returns the next token on the stack
	 * @return The next token on the stack 
	 */
	public Token.Terminal getNext() {
		if((index + 1) < tokens.size())
			return tokens.get(index++);
		else {
			index++;
			return new Token.Terminal('$');
		}
	}
	
	/**
	 * Returns true if there are more characters on the stack, false otherwise.
	 * @return True if more characters remain on the stack
	 */
	public boolean hasNext() {
		return (index < tokens.size());
	}
	
	/**
	 * Returns true if the string was accepted, false otherwise.
	 * @return True if the string was accepted
	 */
	private boolean isAccepted(char a) {
		for(int i = 0; i < language.length; i++) {
			if(a == language[i])
				return true;
		}
		return false;
	}
	
	/**
	 * Thrown if a bad character is encountered during scanning.
	 * @author DPrince
	 */
	public class BadCharacterException extends Exception {
		private static final long serialVersionUID = -2314095556417052538L;
    
    /* instance variables to hold error information */
    /** The character which produced the error */
    private char  errorChar;
    /** The position of the character in the input string */
    private int   errorIndex;
    
    /**
     * Constructor -- creates the exception
     * @param a The invalid character causing the exception
     * @param index The position of the character in the input string
     */
		public BadCharacterException(char a, int index) {
		  this.errorChar = a;
			this.errorIndex = index;
			
			// super("Scanner encountered an unrecognized character ('" + a + "') at index " + index);
		}
		
		/**
		 * Returns the character causing the problem
		 * @return The character which produced the exception
		 */
		public char getErrorChar() {
		  return this.errorChar;
		}
		
		/**
		 * Returns the index of the invalid character in the input string
		 * @return The index of the invalid character
		 */
		public int getErrorIndex() {
		  return this.errorIndex;
		}
	}
	
	/**
	 * Returns the entire list of terminals produced by the scanner
	 * @return The list of terminal tokens produced
	 */
	public ArrayList<Token.Terminal> getTokens() {
		return tokens;
	}
}
